#include <qua/time.h>
#include <boost/date_time/posix_time/posix_time.hpp>

namespace qua
{
    using boost::posix_time::ptime;
    using boost::posix_time::time_duration;
    using boost::gregorian::date;
    using boost::gregorian::date_duration;
    using boost::posix_time::microsec;
    using boost::posix_time::microsec_clock;
    using boost::posix_time::min_date_time;
    using boost::posix_time::max_date_time;
    using boost::posix_time::not_a_date_time;
    using boost::posix_time::pos_infin;
    using boost::posix_time::neg_infin;

    class time::data
    {
    public:
        data()
            : m_value( min_date_time )
        {
        }

        data( ptime&& time_value )
            : m_value( std::move( time_value ) )
        {
        }

        data( int year, int month, int day, int hour, int minute, int second, int microsecond )
            : m_value( date( year, month, day ), time_duration( hour, minute, second ) )
        {
            if( microsecond ) m_value += microsec( microsecond );
        }

        ptime const& get_time() const {
            return m_value;
        }

        int year() const {
            return m_value.date().year();
        }

        int month() const {
            return m_value.date().month();
        }

        int day() const {
            return m_value.date().day();
        }

        int hour() const {
            return m_value.time_of_day().hours();
        }

        int minute() const {
            return m_value.time_of_day().minutes();
        }

        int second() const {
            return m_value.time_of_day().seconds();
        }

        int millisecond() const {
            return static_cast<int>( m_value.time_of_day().total_milliseconds() % 1000 );
        }

        int microsecond() const {
            return static_cast<int>( m_value.time_of_day().total_microseconds() % 1000000 );
        }

        bool is_inf() const {
            return m_value.is_infinity();
        }

        bool is_pos_inf() const {
            return m_value.is_pos_infinity();
        }

        bool is_neg_inf() const {
            return m_value.is_neg_infinity();
        }

        bool is_not_a_time() const {
            return m_value.is_not_a_date_time();
        }

        time_duration time_of_day() const {
            return m_value.time_of_day();
        }

        time_duration operator - ( data const& other ) const {
            return time_duration( m_value - other.m_value );
        }

        data operator + ( time_duration const& time_delta ) const {
            return data( m_value + time_delta );
        }

        data operator - ( time_duration const& time_delta ) const {
            return data( m_value - time_delta );
        }

        data& operator += ( time_duration const& time_delta ) {
            m_value += time_delta;
            return *this;
        }

        data& operator -= ( time_duration const& time_delta ) {
            m_value -= time_delta;
            return *this;
        }

        bool operator == ( data const& other ) const {
            return m_value == other.m_value;
        }

        bool operator != ( data const& other ) const {
            return m_value != other.m_value;
        }

        bool operator <= ( data const& other ) const {
            return m_value <= other.m_value;
        }

        bool operator >= ( data const& other ) const {
            return m_value >= other.m_value;
        }

        bool operator < ( data const& other ) const {
            return m_value < other.m_value;
        }

        bool operator > ( data const& other ) const {
            return m_value > other.m_value;
        }

    private:
        ptime m_value;
    };

    class time::delta::data
    {
    public:
        data() {
        }

        data( data&& temp )
            : m_delta( std::move( temp.m_delta ) ) {
        }

        data( time_duration&& delta )
            : m_delta( std::move( delta ) ) {
        }

        data( value const& alfa ) {
            add( alfa );
        }

        data( value const& alfa, value const& beta ) {
            add( alfa ), add( beta );
        }

        data( value const& alfa, value const& beta, value const& gamma ) {
            add( alfa ), add( beta ), add( gamma );
        }

        data( value const& alfa, value const& beta, value const& gamma, value const& epsilon ) {
            add( alfa ), add( beta ), add( gamma ), add( epsilon );
        }

        data( value const& alfa, value const& beta, value const& gamma, value const& epsilon, value const& zeta ) {
            add( alfa ), add( beta ), add( gamma ), add( epsilon ), add( zeta );
        }

        data( value const& alfa, value const& beta, value const& gamma, value const& epsilon, value const& zeta, value const& eta ) {
            add( alfa ), add( beta ), add( gamma ), add( epsilon ), add( zeta ), add( eta );
        }

        void add( value const& alfa ) {
            int64_t delta_value = static_cast<int64_t>( alfa );
            switch( alfa.get_type() )
            {
            case value::of_days:
                m_delta += boost::posix_time::hours( static_cast<int>( delta_value * 24 ) );
                break;
            case value::of_hours:
                m_delta += boost::posix_time::hours( static_cast<int>( delta_value ) );
                break;
            case value::of_minutes:
                m_delta += boost::posix_time::minutes( static_cast<int>( delta_value ) );
                break;
            case value::of_seconds:
                m_delta += boost::posix_time::seconds( static_cast<int>( delta_value ) );
                break;
            case value::of_milliseconds:
                m_delta += boost::posix_time::millisec( delta_value );
                break;
            case value::of_microseconds:
                m_delta += boost::posix_time::microsec( delta_value );
                break;
            default:
            	break;
            }
        }

        time_duration const& get_delta() const {
            return m_delta;
        }

        int64_t in_days() const {
            return m_delta.hours() / 24LL;
        }

        int64_t in_hours() const {
            return m_delta.hours();
        }

        int64_t in_minutes() const {
            return m_delta.total_seconds() / 60LL;
        }

        int64_t in_seconds() const {
            return m_delta.total_seconds();
        }

        int64_t in_milliseconds() const {
            return m_delta.total_milliseconds();
        }

        int64_t in_microseconds() const {
            return m_delta.total_microseconds();
        }

        int hour() const {
            return m_delta.hours() % 24;
        }

        int minute() const {
            return m_delta.minutes();
        }

        int second() const {
            return m_delta.seconds();
        }

        int millisecond() const {
            return m_delta.total_milliseconds() % 1000;
        }

        int microsecond() const {
            return m_delta.total_microseconds() % 1000000;
        }

        data& operator = ( data&& temp ) {
            m_delta = std::move( temp.m_delta );
            return *this;
        }

        data operator + ( data const& other ) const {
            return data( m_delta + other.m_delta );
        }

        data operator - ( data const& other ) const {
            return data( m_delta - other.m_delta );
        }

        data& operator += ( data const& other ) {
            m_delta += other.m_delta;
            return *this;
        }

        data& operator -= ( data const& other ) {
            m_delta -= other.m_delta;
            return *this;
        }

        bool operator == ( data const& other ) const {
            return m_delta == other.m_delta;
        }

        bool operator != ( data const& other ) const {
            return m_delta != other.m_delta;
        }

        bool operator <= ( data const& other ) const {
            return m_delta <= other.m_delta;
        }

        bool operator >= ( data const& other ) const {
            return m_delta >= other.m_delta;
        }

        bool operator < ( data const& other ) const {
            return m_delta < other.m_delta;
        }

        bool operator > ( data const& other ) const {
            return m_delta > other.m_delta;
        }

    private:
        time_duration m_delta;
    };

    time::time()
    {
    }

    time::time( time::data* data_ptr )
        : m_data( data_ptr )
    {
    }

    time::time( int year, int month, int day, int hour, int minute, int second, int microsecond )
        : m_data( new data( year, month, day, hour, minute, second, microsecond ) )
    {
    }

    int time::year() const
    {
        return m_data->year();
    }

    int time::month() const
    {
        return m_data->month();
    }

    int time::day() const
    {
        return m_data->day();
    }

    int time::hour() const
    {
        return m_data->hour();
    }

    int time::minute() const
    {
        return m_data->minute();
    }

    int time::second() const
    {
        return m_data->second();
    }

    int time::millisecond() const
    {
        return m_data->millisecond();
    }

    int time::microsecond() const
    {
        return m_data->microsecond();
    }

    time time::now()
    {
        return time( new data( microsec_clock::local_time() ) );
    }

    time time::min()
    {
        return time( new data( ptime( min_date_time ) ) );
    }

    time time::max()
    {
        return time( new data( ptime( max_date_time ) ) );
    }

    time time::pos_inf()
    {
        return time( new data( ptime( pos_infin ) ) );
    }

    time time::neg_inf()
    {
        return time( new data( ptime( neg_infin ) ) );
    }

    time time::not_a_time()
    {
        return time( new data( ptime( not_a_date_time ) ) );
    }

    bool time::is_inf() const
    {
        return m_data->is_inf();
    }

    bool time::is_pos_inf() const
    {
        return m_data->is_pos_inf();
    }

    bool time::is_neg_inf() const
    {
        return m_data->is_neg_inf();
    }

    bool time::is_not_a_time() const
    {
        return m_data->is_not_a_time();
    }

    time::delta time::time_of_day() const
    {
        return delta( new delta::data( m_data->time_of_day() ) );
    }

    time::delta time::operator - ( time const& other ) const
    {
        return delta( new delta::data( *m_data - *other.m_data ) );
    }

    time time::operator + ( time::delta const& other ) const
    {
        return time( new data( *m_data + other.m_data->get_delta() ) );
    }

    time& time::operator += ( time::delta const& other )
    {
        *m_data += other.m_data->get_delta();
        return *this;
    }

    bool time::operator == ( time const& other ) const
    {
        return *m_data == *other.m_data;
    }

    bool time::operator != ( time const& other ) const
    {
        return *m_data != *other.m_data;
    }

    bool time::operator <= ( time const& other ) const
    {
        return *m_data <= *other.m_data;
    }

    bool time::operator >= ( time const& other ) const
    {
        return *m_data >= *other.m_data;
    }

    bool time::operator < ( time const& other ) const
    {
        return *m_data < *other.m_data;
    }

    bool time::operator > ( time const& other ) const
    {
        return *m_data > *other.m_data;
    }

    time::delta::delta()
    {
    }

    time::delta::delta( time::delta&& temp )
        : m_data( std::move( temp.m_data ) )
    {
    }

    time::delta::delta( time::delta::data* data_ptr )
        : m_data( data_ptr )
    {
    }

    time::delta::delta( time::delta::data&& temp_data )
        : m_data( std::move( temp_data ) )
    {
    }

    time::delta::delta( time::delta::value const& alfa )
        : m_data( new data( alfa ) )
    {
    }

    time::delta::delta( time::delta::value const& alfa, time::delta::value const& beta )
        : m_data( new data( alfa, beta ) )
    {
    }

    time::delta::delta( time::delta::value const& alfa, time::delta::value const& beta, time::delta::value const& gamma )
        : m_data( new data( alfa, beta, gamma ) )
    {
    }

    time::delta::delta( time::delta::value const& alfa, time::delta::value const& beta,
                        time::delta::value const& gamma, time::delta::value const& epsilon )
        : m_data( new data( alfa, beta, gamma, epsilon ) )
    {
    }

    time::delta::delta( time::delta::value const& alfa, time::delta::value const& beta, time::delta::value const& gamma,
                        time::delta::value const& epsilon, time::delta::value const& zeta )
        : m_data( new data( alfa, beta, gamma, epsilon, zeta ) )
    {
    }

    time::delta::delta( time::delta::value const& alfa, time::delta::value const& beta, time::delta::value const& gamma,
                        time::delta::value const& epsilon, time::delta::value const& zeta, time::delta::value const& eta )
        : m_data( new data( alfa, beta, gamma, epsilon, zeta, eta ) )
    {
    }

    int64_t time::delta::in_days() const
    {
        return m_data->in_days();
    }

    int64_t time::delta::in_hours() const
    {
        return m_data->in_hours();
    }

    int64_t time::delta::in_minutes() const
    {
        return m_data->in_minutes();
    }

    int64_t time::delta::in_seconds() const
    {
        return m_data->in_seconds();
    }

    int64_t time::delta::in_milliseconds() const
    {
        return m_data->in_milliseconds();
    }

    int64_t time::delta::in_microseconds() const
    {
        return m_data->in_microseconds();
    }

    int time::delta::hour() const
    {
        return m_data->hour();
    }

    int time::delta::minute() const
    {
        return m_data->minute();
    }

    int time::delta::second() const
    {
        return m_data->second();
    }

    int time::delta::millisecond() const
    {
        return m_data->millisecond();
    }

    int time::delta::microsecond() const
    {
        return m_data->microsecond();
    }

    time::delta& time::delta::operator = ( time::delta::value const& alfa )
    {
        *m_data = alfa;
        return *this;
    }

    time::delta& time::delta::operator = ( time::delta&& temp )
    {
        *m_data = std::move( *temp.m_data );
        return *this;
    }

    time::delta time::delta::operator + ( time::delta const& other ) const
    {
        return delta( *m_data + *other.m_data );
    }

    time::delta time::delta::operator - ( time::delta const& other ) const
    {
        return delta( *m_data - *other.m_data );
    }

    time::delta& time::delta::operator += ( time::delta const& other )
    {
        *m_data += *other.m_data;
        return *this;
    }

    time::delta& time::delta::operator -= ( time::delta const& other )
    {
        *m_data -= *other.m_data;
        return *this;
    }

    bool time::delta::operator == ( time::delta const& other ) const
    {
        return *m_data == *other.m_data;
    }

    bool time::delta::operator != ( time::delta const& other ) const
    {
        return *m_data != *other.m_data;
    }

    bool time::delta::operator <= ( time::delta const& other ) const
    {
        return *m_data <= *other.m_data;
    }

    bool time::delta::operator >= ( time::delta const& other ) const
    {
        return *m_data >= *other.m_data;
    }

    bool time::delta::operator < ( time::delta const& other ) const
    {
        return *m_data < *other.m_data;
    }

    bool time::delta::operator > ( time::delta const& other ) const
    {
        return *m_data > *other.m_data;
    }
}
